/*
 * Package il.ac.biu.cs.grossmm.impl.activeData
 * File Mediator.java
 * 
 * This is a part of presence service framework prototype. 
 * See javadoc for more information.
 *  
 * Copyright (C) 2006 Grossmann Mark
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package il.ac.biu.cs.grossmm.impl.activeData;

import il.ac.biu.cs.grossmm.api.OperationFailedException;
import il.ac.biu.cs.grossmm.api.data.NodeType;
import il.ac.biu.cs.grossmm.api.data.Root;
import il.ac.biu.cs.grossmm.api.flow.ConverterProxy;
import il.ac.biu.cs.grossmm.api.flow.NodeEventInspector;
import il.ac.biu.cs.grossmm.api.flow.NodeInspector;
import il.ac.biu.cs.grossmm.api.flow.NotificationProvider;
import il.ac.biu.cs.grossmm.api.flow.Subscriber;
import il.ac.biu.cs.grossmm.api.flow.Status;
import il.ac.biu.cs.grossmm.api.flow.Subscription;
import il.ac.biu.cs.grossmm.api.flow.Track;
import il.ac.biu.cs.grossmm.api.keys.Key;
import il.ac.biu.cs.grossmm.api.keys.KeyPattern;
import il.ac.biu.cs.grossmm.impl.keys.PatternMatcher;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Mediator<Nsrc, Ndst> implements Subscriber<Nsrc>,
		NotificationProvider<Ndst> {
	ConversionProcessor<Nsrc, Ndst> processor;

	ConverterProxy<Nsrc, Ndst> proxy;

	HashedRootNode<Ndst> dstRoot;

	il.ac.biu.cs.grossmm.impl.activeData.Root<Nsrc> srcRoot;

	SubscriptionImpl<Ndst> dstSubscription;

	SubscriptionImpl<Nsrc> srcSubscription;

	Lock lock = new ReentrantLock();

	Condition cond = lock.newCondition();

	boolean unsubscribed = false;

	static MemNodeManpipulator manipulator = new MemNodeManpipulator();

	/**
	 * @param processor
	 * @param dstSubscription
	 * @param srcSubscription
	 */
	public Mediator(ConversionProcessor<Nsrc, Ndst> processor) {
		this.processor = processor;
	}

	public void activated(Key key, Root<Nsrc> root) {
		if (dstSubscription == null)
			return; // activate in setSubscriptions

		srcRoot = ((il.ac.biu.cs.grossmm.impl.activeData.Root<Nsrc>) root);
		NodeInspector srcInspector = srcRoot.readLock();

		try {
			Key dstKey = processor.transformKey(key);

			lock.lock();
			dstRoot = new HashedRootNode<Ndst>(processor.getDestinationType(),
					dstKey);
			dstSubscription.activate(dstRoot);

			// no need to lock dstRoot since noone else has a reference to it

			if (proxy == null) {
				proxy = processor.createProxy(srcRoot, dstRoot, srcInspector,
						manipulator);
				cond.signal();
			}

		} catch (OperationFailedException e) {
			e.printStackTrace();
			dstSubscription.deactivate(Status.ERROR);
			return;
		} finally {
			lock.unlock();
			srcRoot.readUnlock();
		}

		dstSubscription.activate(dstRoot);

		dstRoot.shift();
		dstSubscription.nodeUpdated(MemEventInspector.INSTANCE);
	}

	public void deactivated(Key key, Status state) {
		lock.lock();
		if (proxy != null)
			proxy.release();

		proxy = null;
		dstRoot = null;
		if (dstSubscription != null) // workaround
			dstSubscription.deactivate(state);
		lock.unlock();
	}

	public void notify(Root<Nsrc> srcRoot, NodeEventInspector inspector) {
		// TODO: lock dstRoot

		// assert proxy != null;

		try {
			lock.lock();
			while (proxy == null)
				cond.await();

			lock.unlock();
			proxy.convert(inspector, manipulator);
		} catch (Exception e) {
			e.printStackTrace();
		}

		// TODO: relock

		dstRoot.shift();

		if (dstSubscription.getRoot() == null) {
			System.out.println("null");
		}

		dstSubscription.nodeUpdated(MemEventInspector.INSTANCE);

		// TODO: unlock
	}

	void setSubscriptions(SubscriptionImpl<Nsrc> srcSubscription,
			SubscriptionImpl<Ndst> dstSubscription) {
		this.srcSubscription = srcSubscription;
		this.dstSubscription = dstSubscription;

		srcRoot = srcSubscription.getRoot();

		if (srcRoot != null)
			activated(srcSubscription.getKey(), srcSubscription.getRoot());
		else
			deactivated(srcSubscription.getKey(), srcSubscription.getStatus());
	}

	public DataLayer<Ndst> getDataLayer() {
		return processor.dstLayer;
	}

	public PatternMatcher getMatcher() {
		// TODO Implement getMatcher
		// return null;
		throw new RuntimeException("Illegal method invokation");
	}

	public NodeType<Ndst> getNodeType() {
		throw new RuntimeException("Illegal method invokation");
	}

	public KeyPattern getPattern() {
		throw new RuntimeException("Illegal method invokation");
	}

	public double getPriority() {
		throw new RuntimeException("Illegal method invokation");
	}

	public SubscriptionImpl<Ndst> subscribe(Subscriber<Ndst> subscriber, Key key,
			Track track) {
		throw new RuntimeException("Illegal method invokation");
	}

	public void unsubscribe(Key key) {
		lock.lock();
		if (proxy != null)
			proxy.release();
		
		srcSubscription.remove(this);
		processor.dstLayer.subscriptionClosed(dstSubscription);

		proxy = null;
		dstRoot = null;
		unsubscribed = true;
		cond.signal();
		lock.unlock();
	}

}
